001 /* 002 * Copyright 2006 Stephen J. McConnell. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 013 * implied. 014 * 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package net.dpml.state; 020 021 import java.io.IOException; 022 import java.net.URI; 023 024 import javax.xml.XMLConstants; 025 026 import net.dpml.state.Trigger.TriggerEvent; 027 028 import net.dpml.util.DOM3DocumentBuilder; 029 import net.dpml.util.ElementHelper; 030 031 import org.w3c.dom.Document; 032 import org.w3c.dom.Element; 033 034 /** 035 * Construct a state graph. 036 */ 037 public class StateDecoder 038 { 039 private static final String XML_HEADER = 040 "<?xml version=\"1.0\"?>"; 041 042 private static final String STATE_SCHEMA_URN = "link:xsd:dpml/lang/dpml-state#1.0"; 043 044 private static final String STATE_HEADER = 045 "<state xmlns=\"" 046 + STATE_SCHEMA_URN 047 + "\"" 048 + "\n xmlns:xsi=\"" 049 + XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI 050 + "\">"; 051 052 private static final String STATE_FOOTER = "</state>"; 053 054 private static final DOM3DocumentBuilder BUILDER = new DOM3DocumentBuilder(); 055 056 /** 057 * Load a state graph. 058 * @param uri the graph uri 059 * @return the constructed state graph 060 * @exception IOException if an IO error occurs while reading the 061 * graph data 062 */ 063 public State loadState( URI uri ) throws IOException 064 { 065 if( null == uri ) 066 { 067 throw new NullPointerException( "uri" ); 068 } 069 try 070 { 071 final Document document = BUILDER.parse( uri ); 072 final Element root = document.getDocumentElement(); 073 return buildStateGraph( root ); 074 } 075 catch( Throwable e ) 076 { 077 final String error = 078 "An error while attempting to load a state graph." 079 + "\nURI: " + uri; 080 IOException exception = new IOException( error ); 081 exception.initCause( e ); 082 throw exception; 083 } 084 } 085 086 /** 087 * Build a state graph. 088 * @param element a DOM element representing the root of the state graph 089 * @return the constructed state 090 */ 091 public State buildStateGraph( Element element ) 092 { 093 if( null == element ) 094 { 095 throw new NullPointerException( "element" ); 096 } 097 098 boolean terminal = ElementHelper.getBooleanAttribute( element, "terminal" ); 099 DefaultTransition[] transitions = buildNestedTransitions( element ); 100 DefaultOperation[] operations = buildNestedOperations( element ); 101 DefaultInterface[] interfaces = buildNestedInterfaces( element ); 102 DefaultState[] states = buildNestedStates( 1, element ); 103 DefaultTrigger[] triggers = buildNestedTriggers( element ); 104 return new DefaultState( 105 "root", triggers, transitions, interfaces, operations, states, terminal ); 106 } 107 108 private DefaultTransition[] buildNestedTransitions( Element element ) 109 { 110 Element[] children = ElementHelper.getChildren( element, "transition" ); 111 DefaultTransition[] transitions = new DefaultTransition[ children.length ]; 112 for( int i=0; i<children.length; i++ ) 113 { 114 Element child = children[i]; 115 transitions[i] = buildTransition( child ); 116 } 117 return transitions; 118 } 119 120 private DefaultOperation[] buildNestedOperations( Element element ) 121 { 122 Element[] children = ElementHelper.getChildren( element, "operation" ); 123 DefaultOperation[] operations = new DefaultOperation[ children.length ]; 124 for( int i=0; i<children.length; i++ ) 125 { 126 Element child = children[i]; 127 operations[i] = buildOperation( child ); 128 } 129 return operations; 130 } 131 132 private DefaultInterface[] buildNestedInterfaces( Element element ) 133 { 134 Element[] children = ElementHelper.getChildren( element, "interface" ); 135 DefaultInterface[] interfaces = new DefaultInterface[ children.length ]; 136 for( int i=0; i<children.length; i++ ) 137 { 138 Element child = children[i]; 139 interfaces[i] = buildInterface( child ); 140 } 141 return interfaces; 142 } 143 144 private DefaultState[] buildNestedStates( int n, Element element ) 145 { 146 Element[] children = ElementHelper.getChildren( element, "state" ); 147 DefaultState[] states = new DefaultState[ children.length ]; 148 for( int i=0; i<children.length; i++ ) 149 { 150 Element child = children[i]; 151 states[i] = buildState( n, child ); 152 } 153 return states; 154 } 155 156 private DefaultTrigger[] buildNestedTriggers( Element element ) 157 { 158 Element[] children = ElementHelper.getChildren( element, "trigger" ); 159 DefaultTrigger[] triggers = new DefaultTrigger[ children.length ]; 160 for( int i=0; i<children.length; i++ ) 161 { 162 Element child = children[i]; 163 triggers[i] = buildTrigger( child ); 164 } 165 return triggers; 166 } 167 168 private DefaultTransition buildTransition( Element element ) 169 { 170 String name = ElementHelper.getAttribute( element, "name" ); 171 String target = ElementHelper.getAttribute( element, "target" ); 172 Element child = ElementHelper.getChild( element, "operation" ); 173 DefaultOperation operation = buildOperation( child ); 174 return new DefaultTransition( name, target, operation ); 175 } 176 177 private DefaultOperation buildOperation( Element element ) 178 { 179 if( null == element ) 180 { 181 return null; 182 } 183 String name = ElementHelper.getAttribute( element, "name" ); 184 String method = ElementHelper.getAttribute( element, "method" ); 185 return new DefaultOperation( name, method ); 186 } 187 188 private DefaultInterface buildInterface( Element element ) 189 { 190 String classname = ElementHelper.getAttribute( element, "class" ); 191 return new DefaultInterface( classname ); 192 } 193 194 private DefaultState buildState( int n, Element element ) 195 { 196 String name = ElementHelper.getAttribute( element, "name" ); 197 boolean terminal = ElementHelper.getBooleanAttribute( element, "terminal" ); 198 DefaultTransition[] transitions = buildNestedTransitions( element ); 199 DefaultOperation[] operations = buildNestedOperations( element ); 200 DefaultInterface[] interfaces = buildNestedInterfaces( element ); 201 DefaultState[] states = buildNestedStates( n+1, element ); 202 DefaultTrigger[] triggers = buildNestedTriggers( element ); 203 return new DefaultState( 204 name, triggers, transitions, interfaces, operations, states, terminal ); 205 } 206 207 private DefaultTrigger buildTrigger( Element element ) 208 { 209 String type = ElementHelper.getAttribute( element, "event" ); 210 TriggerEvent event = TriggerEvent.parse( type ); 211 Element child = getSingleNestedElement( element ); 212 Action action = buildAction( child ); 213 return new DefaultTrigger( event, action ); 214 } 215 216 private Action buildAction( Element element ) 217 { 218 String name = element.getTagName(); 219 if( name.equals( "transition" ) ) 220 { 221 return buildTransition( element ); 222 } 223 else if( name.equals( "operation" ) ) 224 { 225 return buildOperation( element ); 226 } 227 else if( name.equals( "apply" ) ) 228 { 229 String id = ElementHelper.getAttribute( element, "id" ); 230 return new ApplyAction( id ); 231 } 232 else if( name.equals( "exec" ) ) 233 { 234 String id = ElementHelper.getAttribute( element, "id" ); 235 return new ExecAction( id ); 236 } 237 else 238 { 239 final String error = 240 "Illegal element name [" 241 + name 242 + "] supplied to action builder."; 243 throw new StateBuilderRuntimeException( error ); 244 } 245 } 246 247 private Element getSingleNestedElement( Element parent ) 248 { 249 if( null == parent ) 250 { 251 throw new NullPointerException( "parent" ); 252 } 253 else 254 { 255 Element[] children = ElementHelper.getChildren( parent ); 256 if( children.length == 1 ) 257 { 258 return children[0]; 259 } 260 else 261 { 262 final String error = 263 "Parent element does not contain a single child."; 264 throw new IllegalArgumentException( error ); 265 } 266 } 267 } 268 }